<?
/**
 * Copyright (c) 2009, Jacek Karczmarczyk
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of the author nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * PHP Version 5
 *
 * @package Larchana_Questionnaire
 * @version 0.1
 * @author Jacek Karczmarczyk <jacek@karczmarczyk.pl>
 */

abstract class Larchana_Questionnaire_Question_Abstract
{
	/**
	 * Question storage
	 * @var Larchana_Questionnaire_Question_Storage_Interface
	 */
	protected $_storage = null;

	/**
	 * Question type
	 * @var string
	 */
	protected $_type;

	/**
	 * Is question required?
	 * @var boolean
	 */
	protected $_required = false;

	/**
	 * Question definition
	 * @var array
	 */
	protected $_definition = array();

	/**
	 * Registered question types
	 * @var array 
	 */
	static protected $_types = array();

	/**
	 * Constructor. Loads question definition from storage or from array
	 *
	 * @param Larchana_Questionnaire_Question_Storage_Interface|array $definitionOrStorage
	 */
	protected function __construct($definitionOrStorage = null)
	{
		if ($definitionOrStorage instanceof Larchana_Questionnaire_Question_Storage_Interface)
		{
			$this->_storage = $definitionOrStorage;
			$this->_type = strtolower($this->_storage->type);
			$this->setRequred($this->_storage->required);
			$this->setDefinition((array)unserialize($this->_storage->definition));
		}
		else
		{
			$this->_type = strtolower($definitionOrStorage['type']);
			$this->setRequired($definitionOrStorage['required']);
			$this->setDefinition($definitionOrStorage['definition']);
		}

		if ($this->_type != strtolower(array_pop(explode('_', get_class($this)))))
			throw new Exception("Question type mismatch");

		if (!self::isRegisteredType($this->_type))
			throw new Exception(sprintf("Unknown question type %s'", $this->_type));
	}

	/**
	 * Creates a question object defined by the parameters
	 *
	 * @param string  $type Question type
	 * @param boolean $required Is question required
	 * @param array   $definition Question definition
	 * @return Larchana_Questionnaire_Question_Abstract Question
	 * @todo Allow other prefixed than Larchana_Questionnaire_Question_
	 */
	final public static function factory($type, $required, array $definition)
	{
		$class = 'Larchana_Questionnaire_Question_'.ucwords($type);
		if (!class_exists($class))
			throw new Exception(sprintf("Class '%s' does not exists", $class));

		return new $class(array('type'=>$type, 'required'=>$required, 'definition'=>$definition));
	}

	/**
	 * Loads question from storage
	 * 
	 * @param Larchana_Questionnaire_Question_Storage_Interface $storage Question storage
	 * @return Larchana_Questionnaire_Question_Abstract Question
	 */
	final public static function loadFromStorage(Larchana_Questionnaire_Question_Storage_Interface $storage)
	{
		$type = strtolower($storage->type);

		$class = 'Larchana_Questionnaire_Question_'.ucwords($type);
		if (!class_exists($class))
			throw new Exception(sprintf('Class "%s" does not exists', $class));

		return new $class($storage);
	}

	/**
	 * Registration of allowed question types
	 *
	 * @param string $name Question type
	 */
	public static function registerType($name)
	{
		self::$_types[(string)$name] = (string)$name;
	}

	/**
	 * Checks if the type of question is registered
	 * @param string $name Question type
	 * @return boolea True if the question type is registered, else false
	 */
	public static function isRegisteredType($name)
	{
		return array_key_exists($name, self::$_types);
	}

	/**
	 * Creates editor for the question
	 *
	 * @param string||null $prefix Element name prefix
	 * @return Zend_Form Editor form
	 */
	public function createEditor($prefix = '', $forceType = null)
	{
		$editor = new Zend_Form();

		if ($forceType && $forceType != $this->getType())
		{
			$question = self::factory($forceType, $this->getRequired(), $this->getDefinition());
		}
		else
		{
			$question = $this;
		}

		// $editor->setTranslator()

		$types = array();
		foreach(self::$_types as $type) $types[$type] = $type;
		$element = $editor->createElement('select', $prefix.'type', array('multiOptions'=>$types));
		$element->setRequired(true);
		$element->setValue($question->getType());
		$element->setAttrib('class', 'type');
		$editor->addElement($element);

		if (!$question->isDecorator())
		{
			$element = $editor->createElement('checkbox', $prefix.'required');
			$element->setLabel('required');
			$element->setValue($question->getRequired());
			$editor->addElement($element);
		}

		$structure = $question->getStructure();
		foreach($structure['elements'] as $name=>$def)
		{
			$value = $question->$name;
			if (isset($def['array']) && $def['array'])
			{
				for($i=0; $i<20; $i++)
				{
					$element = $editor->createElement('text', $prefix.$name.'_'.$i);
					$element->setLabel($name);
					if (isset($value[$i])) $element->setValue($value[$i]);
					$editor->addElement($element);
				}
			}
			else
			{
				$element = $editor->createElement('text', $prefix.$name);
				$element->setLabel($name);
				$element->setValue($value);
				if (isset($def['required']) && $def['required'])
				{
					$element->setRequired();
				}
				$editor->addElement($element);
			}
		}

		$editor->addElement('submit', $prefix.'save', array('label'=>'zapisz'));

		return $editor;
	}

	/**
	 * Imports the question definition from the POST values
	 *
	 * @param array $values POST values
	 * @param string $prefix Prefix
	 * @return array Type, required and definition
	 * @see createEditor()
	 */
	public static function importPostValues(array $values, $prefix = '')
	{
		$type = @$values[$prefix.'type'];
		$required = (boolean)@$values[$prefix.'required'];
		$question = self::factory($type, $required, array());

		$structure = $question->getStructure();
		foreach($structure['elements'] as $name=>$element)
		{
			if (isset($element['array']) && $element['array'])
			{
				$data = array();
				for ($i=0; $i<20; $i++)
				{
					if (isset($values[$prefix.$name.'_'.$i]) && (string)$values[$prefix.$name.'_'.$i] !== '')
					{
						$data[$i] = $values[$prefix.$name.'_'.$i];
					}
				}
				$question->$name = $data;
			}
			else
			{
				$question->$name = @$values[$prefix.$name];
			}
		}
		
		return array(
			'type'=>$question->getType(),
			'required'=>$question->getRequired(),
			'definition'=>$question->getDefinition()
		);
	}


	/**
	 * Checks wether the question is required
	 *
	 * @return boolean True if required
	 */
	public function getRequired()
	{
		return $this->_required;
	}

	/**
	 * Sets the "required" attribute
	 *
	 * @param boolean $required Value
	 * @return Larchana_Questionnaire_Question_Abstract Provides fluent interface
	 */
	public function setRequired($required = true)
	{
		$this->_required = (boolean)$required;
		if ($storage = $this->getStorage())
			$storage->required = (boolean)$required;
		return $this;
	}

	/**
	 * Creates the form element
	 *
	 * @return Zend_Form_Element Form element
	 */
	public function createElement(Zend_Form $form, $name)
	{
		$element = $form->createElement("Template", $name);
		$element->setRequired($this->getRequired());
		$element->setDecorators(array('Template'));
		$element->setAttrib('template', $this->getTemplateName());

		$structure = $this->getStructure();
		foreach($structure['elements'] as $name=>$foo)
		{
			if ($name == 'label')
				$element->setLabel($this->label);
			else
				$element->setAttrib($name, $this->$name);
		}

		return $element;
	}

	/**
	 * Creates the form with only one question
	 *
	 * @return Zend_Form Element form
	 */
	public function createPreview()
	{
		$form = new Zend_Form();
		$form->addPrefixPath('Larchana_Form_Element', 'Larchana/Form/Element/', 'element');
		$form->addElementPrefixPath('Larchana_Form_Decorator', 'Larchana/Form/Decorator/', 'decorator');
		$form->setMethod('post');
		$form->removeDecorator('HtmlTag');
		$form->addElement($this->createElement($form, uniqid('p_')));
		return $form;
	}


	/**
	 * Checks wether the question is decorator (like header or warning etc)
	 *
	 * @return boolean True if decorator, else false
	 */
	public function isDecorator()
	{
		$structure = $this->getStructure();
		return array_key_exists('decorator', $structure) ? $structure['decorator'] : false;
	}

	/**
	 * Returns the question template name
	 * 
	 * @return string Template name
	 */
	public function getTemplateName()
	{
		$structure = $this->getStructure();
		return array_key_exists('template', $structure) ? $structure['template'] : $this->getType();
	}

	/**
	 * Returns the question type
	 *
	 * @return string Question type
	 */
	public function getType()
	{
		return $this->_type;
	}

	/**
	 * Validates the input value
	 * @param string|mixed $value Value
	 * @return boolean True if valid
	 */
	public function isValid($value)
	{
		return $this->createElement()->isValid($value);
	}

	/**
	 * Storage getter
	 * 
	 * @return Larchana_Questionnaire_Question_Storage_Interface storage
	 */
	public function getStorage()
	{
		return $this->_storage;
	}

	/**
	 * Gets the definition attribute value (i.e. label, description etc)
	 *
	 * @param string $name Attribute name
	 * @return mixed Attribute value
	 */
	public function __get($name)
	{
		if (!$this->__isset($name))
		{
			throw new Exception(get_class().": Attribute $name is not defined in question structure");
		}

		return array_key_exists($name, $this->_definition) ? $this->_definition[$name] : null;
	}

	/**
	 * Sets the definition attribute
	 *
	 * @param string $name Attribute name
	 * @param array $value Attribute value
	 */
	public function __set($name, $value)
	{
		if (!$this->__isset($name))
		{
			throw new Exception(get_class().": Attribute $name is not defined in question structure");
		}

		$this->_definition[$name] = $value;
		if ($storage = $this->getStorage())
			$storage->definition = serialize($this->_definition);
	}

	/**
	 * Checks wether the definition attribute exists
	 *
	 * @param string $name Attribute name
	 * @return boolean True if attribute exists
	 */
	public function __isset($name)
	{
		$structure = $this->getStructure();
		return array_key_exists('elements', $structure) && array_key_exists($name, $structure['elements']);
	}

	/**
	 * Sets the question definition
	 *
	 * @param array $definition Question definition
	 */
	public function setDefinition(array $definition)
	{
		foreach($definition as $key=>$value) $this->$key = $value;

		if ($storage = $this->getStorage())
			$storage->definition = serialize($this->getDefinition());
	}

	/**
	 * Returns the definition of the question
	 *
	 * @return array
	 */
	public function getDefinition()
	{
		return $this->_definition;
	}

	abstract public function getStructure();
}

